/*
 * Copyright (c) 2009-2010 Panxiaobo
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package pxb.android.dex2jar.optimize.c;

import java.util.List;

import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Value;

/**
 * @author Panxiaobo
 * 
 */
public class DexInterpreter extends CBasicInterpreter {

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter# newValue(org.objectweb.asm.Type)
     */
    @Override
    public Value newValue(Type type) {
        // if (type == Type.INT_TYPE || type == Type.LONG_TYPE) {
        // return new BasicValue(null);
        // }
        return super.newValue(type);
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter# binaryOperation
     * (pxb.android.dex2jar.org.objectweb.asm.tree.AbstractInsnNode,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value)
     */
    @Override
    public Value binaryOperation(AbstractInsnNode insn, Value value1, Value value2) throws AnalyzerException {
        switch (insn.getOpcode()) {
        case PUTFIELD:
            FieldInsnNode fieldInsnNode = (FieldInsnNode) insn;
            if (((CBasicValue) value1).getType() == null) {
                ((CBasicValue) value1).setType(Type.getType(fieldInsnNode.owner));
            }
            if (((CBasicValue) value2).getType() == null) {
                ((CBasicValue) value2).setType(Type.getType(fieldInsnNode.desc));
            }
            break;
        // case IALOAD:
        // case BALOAD:
        // case CALOAD:
        // case SALOAD:
        case IADD:
        case ISUB:
        case IMUL:
        case IDIV:
        case IREM:
        case ISHL:
        case ISHR:
        case IUSHR:
        case IAND:
        case IOR:
        case IXOR:
            if (((CBasicValue) value1).getType() == null) {
                ((CBasicValue) value1).setType(Type.INT_TYPE);
            }
            if (((CBasicValue) value2).getType() == null) {
                ((CBasicValue) value2).setType(Type.INT_TYPE);
            }
            break;
        case FALOAD:
        case FADD:
        case FSUB:
        case FMUL:
        case FDIV:
        case FREM:
        case FCMPL:
        case FCMPG:
            if (((CBasicValue) value1).getType() == null) {
                ((CBasicValue) value1).setType(Type.FLOAT_TYPE);
            }
            if (((CBasicValue) value2).getType() == null) {
                ((CBasicValue) value2).setType(Type.FLOAT_TYPE);
            }
            break;
        case LALOAD:
        case LADD:
        case LSUB:
        case LMUL:
        case LDIV:
        case LREM:
        case LSHL:
        case LSHR:
        case LUSHR:
        case LAND:
        case LOR:
        case LXOR:
        case LCMP:
            if (((CBasicValue) value1).getType() == null) {
                ((CBasicValue) value1).setType(Type.LONG_TYPE);
            }
            if (((CBasicValue) value2).getType() == null) {
                ((CBasicValue) value2).setType(Type.LONG_TYPE);
            }
            break;
        case DALOAD:
        case DADD:
        case DSUB:
        case DMUL:
        case DDIV:
        case DREM:
        case DCMPL:
        case DCMPG:
            if (((CBasicValue) value1).getType() == null) {
                ((CBasicValue) value1).setType(Type.DOUBLE_TYPE);
            }
            if (((CBasicValue) value2).getType() == null) {
                ((CBasicValue) value2).setType(Type.DOUBLE_TYPE);
            }
            break;
        // case AALOAD:
        // return BasicValue.REFERENCE_VALUE;
        case IF_ICMPEQ:
        case IF_ICMPNE:
        case IF_ICMPLT:
        case IF_ICMPGE:
        case IF_ICMPGT:
        case IF_ICMPLE:
            if (((CBasicValue) value1).getType() == null && ((CBasicValue) value2).getType() != null) {
                ((CBasicValue) value1).setType(((CBasicValue) value2).getType());
            }
            if (((CBasicValue) value2).getType() == null && ((CBasicValue) value1).getType() != null) {
                ((CBasicValue) value2).setType(((CBasicValue) value1).getType());
            }
            break;
        case IF_ACMPEQ:
        case IF_ACMPNE:
        }

        return super.binaryOperation(insn, value1, value2);
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter# copyOperation
     * (pxb.android.dex2jar.org.objectweb.asm.tree.AbstractInsnNode,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value)
     */
    @Override
    public Value copyOperation(AbstractInsnNode insn, Value value) throws AnalyzerException {

        // if(Util.isRead(insn)&& value ==null){
        // System.out.println();
        // }

        switch (insn.getOpcode()) {
        case FLOAD:
        case FSTORE:
            if (((CBasicValue) value).getType() == null) {
                ((CBasicValue) value).setType(Type.FLOAT_TYPE);
                break;
            }
        case DLOAD:
        case DSTORE:
            if (((CBasicValue) value).getType() == null) {
                ((CBasicValue) value).setType(Type.DOUBLE_TYPE);
                break;
            }
        }

        return super.copyOperation(insn, value);
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter# ternaryOperation
     * (pxb.android.dex2jar.org.objectweb.asm.tree.AbstractInsnNode,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value)
     */
    @Override
    public Value ternaryOperation(AbstractInsnNode insn, Value value1, Value value2, Value value3) throws AnalyzerException {
        // TODO Auto-generated method stub
        return super.ternaryOperation(insn, value1, value2, value3);
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter#
     * newOperation(pxb.android.dex2jar.org.objectweb.asm.tree.AbstractInsnNode)
     */
    @Override
    public Value newOperation(AbstractInsnNode insn) throws AnalyzerException {
        switch (insn.getOpcode()) {
        case ACONST_NULL:
            return newValue(Type.getObjectType("null"));
        case ICONST_M1:
        case ICONST_0:
        case ICONST_1:
        case ICONST_2:
        case ICONST_3:
        case ICONST_4:
        case ICONST_5:
            return new CBasicValue(null);
        case LCONST_0:
        case LCONST_1:
            return new CBasicValue(null);
        case FCONST_0:
        case FCONST_1:
        case FCONST_2:
            return CBasicValue.FLOAT_VALUE;
        case DCONST_0:
        case DCONST_1:
            return CBasicValue.DOUBLE_VALUE;
        case BIPUSH:
        case SIPUSH:
            return new CBasicValue(null);
        case LDC:
            Object cst = ((LdcInsnNode) insn).cst;
            if (cst instanceof Integer) {
                return new CBasicValue(null);
            } else if (cst instanceof Float) {
                return CBasicValue.FLOAT_VALUE;
            } else if (cst instanceof Long) {
                return new CBasicValue(null);
            } else if (cst instanceof Double) {
                return CBasicValue.DOUBLE_VALUE;
            } else if (cst instanceof Type) {
                return newValue(Type.getObjectType("java/lang/Class"));
            } else {
                return newValue(Type.getType(cst.getClass()));
            }
        case JSR:
            return CBasicValue.RETURNADDRESS_VALUE;
        case GETSTATIC:
            return newValue(Type.getType(((FieldInsnNode) insn).desc));
        case NEW:
            return newValue(Type.getObjectType(((TypeInsnNode) insn).desc));
        default:
            throw new Error("Internal error.");
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter# unaryOperation
     * (pxb.android.dex2jar.org.objectweb.asm.tree.AbstractInsnNode,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value)
     */
    @Override
    public Value unaryOperation(AbstractInsnNode insn, Value value) throws AnalyzerException {
        if (((CBasicValue) value).getType() == null) {
            switch (insn.getOpcode()) {
            case INEG:
            case IINC:
            case I2D:
            case I2L:
            case I2F:
            case I2B:
            case I2C:
            case I2S:
            case IRETURN:
            case NEWARRAY:
            case ANEWARRAY:
            case TABLESWITCH:
            case LOOKUPSWITCH:
                ((CBasicValue) value).setType(Type.INT_TYPE);
                break;
            case L2D:
            case L2F:
            case L2I:
            case LRETURN:
            case LNEG:
                ((CBasicValue) value).setType(Type.LONG_TYPE);
                break;

            case DNEG:
            case D2F:
            case D2L:
            case D2I:
            case DRETURN:
                ((CBasicValue) value).setType(Type.DOUBLE_TYPE);
                break;
            case FNEG:
            case F2D:
            case F2L:
            case F2I:
            case FRETURN:
                ((CBasicValue) value).setType(Type.FLOAT_TYPE);
                break;
            case PUTSTATIC:
                ((CBasicValue) value).setType(Type.getType(((FieldInsnNode) insn).desc));
                break;
            case GETFIELD:
                ((CBasicValue) value).setType(Type.getType(((FieldInsnNode) insn).owner));
                break;
            }
        }
        if (insn.getOpcode() == GETFIELD) {
            return new CBasicValue(Type.getType(((FieldInsnNode) insn).desc));
        }
        return super.unaryOperation(insn, value);
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter#
     * merge(pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value)
     */
    @Override
    public Value merge(Value v, Value w) {

        if (!(w == null || v == null)) {
            if (((CBasicValue) v).getType() == null ^ ((CBasicValue) w).getType() == null) {
                if (((CBasicValue) v).getType() == null) {
                    ((CBasicValue) v).setType(((CBasicValue) w).getType());
                }
                if (((CBasicValue) w).getType() == null) {
                    ((CBasicValue) w).setType(((CBasicValue) v).getType());
                }
            }
        }
        return super.merge(v, w);
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter# naryOperation
     * (pxb.android.dex2jar.org.objectweb.asm.tree.AbstractInsnNode, java.util.List)
     */
    @Override
    public Value naryOperation(AbstractInsnNode insn, List values) throws AnalyzerException {
        switch (insn.getOpcode()) {
        case INVOKESTATIC: {
            Type[] args = Type.getArgumentTypes(((MethodInsnNode) insn).desc);
            for (int i = 0; i < args.length; i++) {
                Value v = (Value) values.get(i);
                if (((CBasicValue) v).getType() == null) {
                    ((CBasicValue) v).setType(args[i]);
                }
            }
            break;
        }
        case INVOKEDYNAMIC:
        case INVOKEINTERFACE:
        case INVOKESPECIAL:
        case INVOKEVIRTUAL: {
            Type[] args = Type.getArgumentTypes(((MethodInsnNode) insn).desc);
            Value v = (Value) values.get(0);
            if (((CBasicValue) v).getType() == null) {
                ((CBasicValue) v).setType(Type.getType(((MethodInsnNode) insn).owner));
            }
            for (int i = 0; i < args.length; i++) {
                v = (Value) values.get(i + 1);
                if (((CBasicValue) v).getType() == null) {
                    ((CBasicValue) v).setType(args[i]);
                }
            }
            break;
        }
        }

        if (insn.getOpcode() == MULTIANEWARRAY) {
            return newValue(Type.getType(((MultiANewArrayInsnNode) insn).desc));
        } else {
            return new CBasicValue(Type.getReturnType(((MethodInsnNode) insn).desc));
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see pxb.android.dex2jar.org.objectweb.asm.tree.analysis.BasicInterpreter# returnOperation
     * (pxb.android.dex2jar.org.objectweb.asm.tree.AbstractInsnNode,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value,
     * pxb.android.dex2jar.org.objectweb.asm.tree.analysis.Value)
     */
    @Override
    public void returnOperation(AbstractInsnNode insn, Value value, Value expected) throws AnalyzerException {
        if (((CBasicValue) value).getType() == null) {
            ((CBasicValue) value).setType(((CBasicValue) expected).getType());
        }

        super.returnOperation(insn, value, expected);
    }

}
